Drawing Bézier Curves II: Words

This is version two of Drawing Bézier Curves. It moves forward from the first version, which ended with successfully rotating, translating, scaling and rendering a swatch. The swatch is the basic shape of which everything else will be constructed.

Version one successfully manages the swatch. Now I need to manage letters made of multiple swatches. The Hebrew letters used are made of semi-rectangular blocks or swatches consisting of two quadratic Bézier curves sharing the same endpoints. The letters needed for this project are chet, waw, he, dalet, alef, and yod.

י א ד ה ו ח

Below are the two words that will recursively produce the desired final picture. I am avoiding calling it art. These two words at the lowest level will be made from letters, themselves made from one, two or three swatches. Tiny versions of these words will be arranged to produce larger versions of the same words. These larger words will again be arranged to produce a larger version of the same two words. This is the plan.

The text to be successively embedded

The letters will look significantly different than those shown above as they are all made from the swatches successfully created in version one.

After writing the swatch object the next challenge is rewriting the letter object in light of a swatch object. Successfully getting the correct size, orientation and location info to the swatch so it can render itself is front and center the crux of the problem. At some point the letters will be created in order to makeup a word. The location of the letter within the word will also need to be received by the letter object and incorporated in the information sent to the swatches.

Once the letter object is rewritten each of the six letters must be defined. The various functions currently written must work for multiple swatches making up a single letter. Five new letters need to be defined: dalet, he, aleph, waw & chet. The letters will need to store only the swatches and their locations, orientations, and sizes. The relative sizes of the swatches making up each letter are initially: yod - 1; dalet - 2, 2; chet - 2, 2, 2; aleph - 1.5, 2.5, 1; he - 1, 2, 2; & waw - 1, 2. Relative locations need to be the same across letters, i.e. the yod needs to be moved up in the SVG window.

The letters will initially be made only of swatches. Two of the six letters currently planned may need some graphics besides swatches. The aleph is often rendered with a fine line connecting the upper right small swatch to the long central swatch. The waw is usually represented with a wide solid line as its descending part on the right side. Again I will first try imitating this with a swatch and hope for the best.

Larger letters would only be needed to make templates for locating the words from which it will be composed. Each letter will need to be "made" and then rotated and translated to match the word's orientation and location. (Though all rotations begin at the word level or higher.) Once things have been rotated and translated at these two levels and then at the POP level, the final SVG will be generated.

Once we have the generic letters, we need to be able to render them as a unit, a word. The letters will now need a location different from that used to make the generic individual letter. A word object is needed for this level. Words will automatically include the appropriate spacing between letters.

Finally, a picture object is required. This object is made from words, with each word sized, oriented, and translated appropriately. The picture generated is, of course, just one of the same words, but with no outline.

There is a final level in this plan. It is not clear if it needs its own object. This level is a picture of a picture, a POP. Pictures are assembled to generate a higher level POP that represents the same picture it is assembled from. An orientation, position, and size is needed for each picture within the POP. These orientations and locations are relatively the same as those used for assembling pictures from words. That is why it is not clear if another object is needed.

This recursive level building is seen most clearly with the rendering process. Telling a POP to render itself causes the POP to tell the pictures composing it to render themselves. Each of these pictures will tell the words composing it to render themselves. Each of these words will tell the letters they are made of to render themselves. Finally, when carried to the ultimate extreme, each letter will tell its component swatches to render themselves.

This raises the real conundrum with which I am now wrestling. How does one propagate the various rotations and translations down through the various levels to finally arrive at swatches that render themselves in the appropriate location and orientation?

Task one is rewriting swatch as an object that can produce its own SVG. Each letter, when told to produce its SVG, will tell its component swatch objects to write their own SVG. The letters will hold instructions on how to orient and locate each of their component swatches. My only concern with this approach is that by the end of the day we will end up with thousands of swatch objects. Sticking with rendering at the level of letters cuts that number by a factor of 2 1/3. We will start with swatch objects.

The swatch object was defined. It contains the control point matrix for the two hard coded Bézier curves making up the basic swatch. It has a getter to retrieve the basicSwatch array. It also has a setter to modify this array if ever needed. The swatch object has one further method to create its own SVG code. This method requires three parameters: the degrees of rotation, the scale factor to size the swatch, and the array of two points for translating in the x- and y-directions. The order of events is rotating the basic swatch, scaling the rotated swatch, and translating the rotated and scaled swatch. This method returns the SVG code for the two curves. It does not include any opening or closing SVG.

Onto the new improved letter object. The letter object should have the following properties: name, number of swatches, and array of swatch information. The swatch information consists of rotation, size relative to the other swatches in this letter, and location also relative to the other swatches in this letter. It will have methods to produce its SVG code (or send the relevant info to the swatch objects), set its properties, and get its properties. The method which produces the letter's SVG code will take parameters that modify its swatch information array so it is drawn in the correct location and at the correct size to fit in a word.

Swatches rotate around their center of mass by default. Letters will rotate around their center of mass and words around their center of mass. When a word is told to render itself it sends its location, orientation and size data to its letters. This information has to be translated so the letters can use it in their own frames of reference. The same has to happen when the letters send their info in their frame of reference to the swatches. At this lowest level the info needs to be converted to the swatch's frame of reference. As an example, rotating a letter with two swatches, turns them as a unit. The two individual swatches experience both a rotation and a translation. This is a matrix manipulation of some sort.

Pulled out my "Elementary Linear Algebra" text by Bernard Kolman, © 1970. This was not very helpful, but triggered a few memories. Drawing out the problem may have helped me find the answer. A swatch has a center of mass, CoM. The letters will be rotated around their own center of mass, CoML. Rotating the swatch around CoML is equivalent to a translation of CoM followed by a rotating the swatch around CoM. To rotate the swatch around a point off its CoM, first rotate the point with the rotatePt() function. Translate the swatch to the new CoM and then rotate the swatch around this new CoM. Rotating the CoM around the CoML is accomplished by translating the CoM to the CoML, multiplying by the rotation matrix, and then reverse translating the CoM.

The rotatePt function was modified to accomplish rotation around any origin point. It is now parallel to the rotateArr function. TransArr is already setup to shift the entire swatch to the new CoM. It just requires that the x- and y-differences between the two CoM's are passed as parameters for the translation. The swatch function, makeSVG, needs to add the CoML as a parameter. If this parameter is present, then the calculation is modified to handle this off CoM rotation.

I am struggling with how to rotate around an arbitrary point. It is already necessary to translate the swatch array (or point) to the coordinate origin in order to perform a rotation. To rotate around an arbitrary point, the swatch array must be translated to the point, this point translated to the origin, the array (or point) rotated... Not True!!! I have been rotating points around the center of mass. This is not arbitrary, but the same formula applies. To rotate a point B around any arbitrary point A use the following two equations for rotated B ≡ B'.

B'x = ((Bx - Ax)cosθ - (By - Ay)sinθ) + Ax

B'y = ((Bx - Ax)sinθ + (By - Ay)cosθ) + Ay

The basicSwatch object was modified to include a new property, ctrOfMass, and a getter for ctrOfMass. This is of course the center of mass of the basic swatch and may be used frequently. The createLetters() function needs to be modified next. This requires modifying the letter{} object. The letter object needs to have a place to store spacing before and after the letter. This varies with letter and needs to be consistent as the letters are placed in words.

On further thought only spaceLeft was added as a property. The word object should also have a spaceLeft property. I now need to define the swatches for each letter. I need to work first with yod. It will be used to make sure I can actually create a letter and render it to the screen.

I have written two different ways to fill the already initialized letters with data. The first was letter by letter. Each letter has its own function. The characteristics that define the letter's swatches are included in the function. I also wrote a very simple function that will create any letter given the appropriate information. This includes sending the characteristics of each swatch in an array of arrays. This is probably the preferred method as it minimizes the number of functions. Also if the process needs to be changed it can be done in only one spot.

Of course this immediately happened when I realized I had not included setting the spaceLeft property.

Attempted the first code run through this morning. After fixing the obvious typos, errors related to the various objects started to appear. Whether this is due to not properly initializing the objects or to not using object prototypes is unclear. My first attempt at fixing the issues is rearranging the code. Clicking the button will call a small function that gets the input. This function will call the main function, which will contain the swatch object at least.

The function calcOutput() called by hitting the "Render Output" button now does three things. It gathers the input from the user, calls main(), and writes the returned SVG to the browser window. The function main() has gathered a number of activities. the swatch object now resides within main() where it can be used by all of the letter objects. These are initialized next. Then the new letters are filled. The base letter object sits outside of main(). The final major action of main is to render a letter. It does this by looking at its first parameter, lett, which holds the desired letter. This calls the letter object's method, createSVG(). Letter.createSVG() calls the swatches createSVG() method. This method calls makeSVG(), returning the SVG for the desired swatch. The SVG traverses back up the call hierarchy to calcOutput() and is rendered on the screen.

The first error received says that spread requires that the iterable not be null or undefined. It refers to the createLetter() function. This is where the letter's swatches are added one-by-one to the new letter. I was creating the letters incorrectly. The letter object was turned into a prototype. Initializing the letters was done with new Letter(). The createLetter() function was skipped and the letters were initialized with data as they were created.

Numerous bugs were fixed. They were mostly associated with using a point as a function parameter. Sometimes an array was used and sometimes two scalars were used. The yod is now printed to the screen, but not rotated appropriately. Unfortunately, all of the letters render only the basic swatch. A small mistake here and there including "/svg" added during swatch translation to SVG. So only the first swatch was appearing on screen. Now I need to move the various swatches around so they look like a letter.

Below the letters are represented as they now appear: yod, dalet, chet, aleph, he, and waw. They definitely need some tweaking, but they are acceptable.

Each letter needs its own center of mass for rotation; this is not a monospaced font. A center of mass was added to each letter. These were calculated from the above SVG extrema for each letter. Three are identical. These centers of mass associated with a letter need to be translated when the letter is translated. Otherwise any further rotations will be nonsensical.

The next task is rewriting the createSVG() code in the letter. This code does two things. It sends each swatch off to swatch.createSVG, which rotates, scales and translates the array. The code sends this modified array off to the function makeSVG(), where the array is turned into SVG. The way things are now implemented, the rotations, scaling factors, and translations from the individual letter are handled, but no user input can be accommodated. It is not yet clear to me how to manage multiple levels of rotating, scaling and translating. These transformations do not commute, (R1 >> T1) >> (R2 >> T2) ≠ (R2 >> T2) >> (R1 >> T1). This is because the rotations do not usually even utilize the same center of rotation. The letter needs to be rotated and translated last to insure its integrity and fit within its word.

This means that the current order of moving and turning into SVG needs to be modified. The entire process is further complicated by the need to go beyond word to picture, where the words will be moved around. The plan includes a picture within a picture level as well. So I need a way to store the movements and then implement them in the correct order. Picture2, the highest level picture is composed of Picture1's. Picture1 is composed of Words, while Words are composed of Letters. Finally, Letters are composed of Swatches.

An array should be constructed of the different movements corresponding to each of the levels described above: [[Letter],[Word],[Picture1],[Picture2]]. At the Swatch level this array will be walked through from Letter to Picture2. Each level will cause the Swatch to recalculate its four control points. After walking through this array the Swatch will call makeSVG() and render itself as SVG code. A global, NumOfLevels, will track the number of extant levels. Each letter will need a copy of this array, but [Word], [Picture1], and [Picture2] are somewhat constant across Letters. That is each Letter has a constant set of Swatches, each of which has a fixed position. Similarly, a Word has eight letters and each letter is in a fixed position within that word. All Words are identical except for their position.

Each Picture1 will be made of many, maybe hundreds of Words. Each Word will have a unique position within Picture1. Picture2 is made of many Picture1's. Each Picture1 will have a unique position within Picture2. But the Words in each Picture1 will have a fixed position relative to its Picture1. So the Words in the second and all subsequent versions of Picture1 will need access to the same relative position information as the first version of Picture1. So we have some constants that will lower the burden of data carried by each letter.

The current plan is for this page to conclude with successfully rendering a word. (Word here actually means two lexical words, but they will always be considered as a unit in the programming Word.) So the swatch createSVG needs to be rewritten so it will perform a series of calculations based on the array it is passed. A Word object needs to be written that will contain the letters and their positions within the Word.

As a first step a few things were rewritten to accept user input. This is now functioning. Something realized earlier, but not followed up on, is the need to change the center of mass if the letter is translated. Otherwise any future rotations will be around the incorrect center of mass.

Some time was also spent tweaking the letters. They are improved, but to make them look correct a flip function is needed. The vertical swatches on the right sides of dalet, chet, he, & waw need to be flipped about the long axis. That is a mirror image of the swatch needs to be used. I am struggling with how best to modify the Letter prototype to accomplish this. One approach is during letter creation. The parameters governing swatch creation will be increased by one, the number of the swatch to be flipped. This can be checked when the swatches are made and the flip() function called. Alternatively, Letter() can be modified so that it has a new property, flip, which will hold the number of the swatch to be flipped. Again this will need to be checked as each of the letter's swatches are created. The second seems simpler.

Flipping was implemented in the following way. In addition to swatch.basicSwatch a new property was added, flippedSwatch. The positionSwatch() function now accepts an additional parameter, toFlip. If this is true basicSwatch is swapped for flippedSwatch. Letter() also has an additional property, flip. Flip holds the number of the swatch to be flipped. Getters and setters are included. Before positionSwatch() is called flip is checked. If it is the correct swatch, then toFlip is set to true. The appropriate letters now set their flipped swatch. After flipping it was discovered that the rotation and translation for the flipped swatch need to be changed as well. An effective implementation and the letters look much better!

One final change to the letters was considered, but temporarily shelved. The angled swatch at the center of the aleph looks too wide to my eye. To scale it appropriately it needs to be scaled differently along the x- and y-axes. Changing this requires changing the number of params for scale(), which has ramifications throughout the program. Too little gain for the effort required.



Create a Letter:







Time to build the word object. Word{} unlike Letter() will not be implemented as a prototype. There is only one word as this project is currently planned. Word{} is really two words, adonai and echad. Since they will only ever be used as a unit, it makes sense to keep them together. The word{} object will be used over and over when constructing a picture. It needs the following properties: center of mass, its letters, positions for all of the letters, and size of the space between the two lexical words. It will need methods for rotating, scaling and translating itself, as well as getting and setting its properties.

Since I don't read Hebrew, the default will be left to right in setting up word{}. It won't matter because the output will always be two lexical words rendered as a unit.

The first major method added to word{} is createWord(). It simply returns an array. The array contains seven arrays, one for each of the letters in the word. Each subarray contains the appropriate letter object and its translation along the x-axis. The translation is made up of the letter's leftSpace property, the letterPos and the shiftAmt. The letterPos is essentially the location of the letter relative to the center of the word. It should compensate for the width of the letters. The shiftAmt is just there to make sure the letters appear in the SVG window.

While writing the word object, it became apparent that main() needed some changes. Currently, it takes the user input concerning letters as parameters. I need it to take the user input concerning a word as parameters. My plan is to send it an array of data, which can contain either letter data or word data. The button selected will inform calcOutput() whether to render a word or a letter.



Create a Word:






Four months later and I am returning to this project. The word functionality is still not working. I am rewriting the word object so it is more self contained. It does not need a create method as there will only be one word used over and over and over. That is not quite right. Properties cannot contain expressions using other properties, at least that is how I interpret my inability to make this work with or without the keyword, this.

The solution that works is to create a method, createWord(), that builds up the array of arrays. It accesses the properties needed with the keyword, this. CreateWord() is called just prior to rendering in this demo. Currently, the word written to the screen is a little wonky looking. Spacing between letters needs to be adjusted. The aleph looks like it is sitting too high. As of yet there are no functions for scaling, translating or rotation.

The spacing has been adjusted so the letters are in reasonable proximity. The aleph was moved down by adjusting params in the letter definition. The COM was not modified. I am not yet convinced it requires modification. This will be tested when I am able to rotate it. The COM of the word was adjusted and will be verified when rotations become available.

To include rotating, translating, and scaling a few things need to be modified. The current order of operations is as follows. CalcOutput() calls main() after collecting the user input. Main() creates the generic letters. The next operation in main(), createWord(), places the letters in order and assigns them a translation only along the x-axis. Also in main() renderWord() takes this array of letters with x-translations and converts the array to SVG.

My first guess at how this should work to take word movement into account is as follows. The created word array with its between letter spacing intact needs to have the results of word rotation, translation, and scaling added. This information can then be passed to the letter.createSVG() function. As before rotating should be done before translating, before scaling.

Rotating a word means moving each of the letters individually. The letters need to be translated to the new position and then rotated. This needs to happen relative to the COM, center of mass, of the word. The only math that needs doing is turning the word rotation into translation along the x- and y-axes. RotatePt() can be used to perform this operation. The COMs of the letter and word are sent to rotatePt(). RotatePt() returns the new point. The difference between this new point and the letter's COM is the needed translation. This would be the case, except the COM has not been updated after the letter was incorporated into the word.

Currently, the renderWord() function tells the letter to createSVG(). It sends the appropriate x-translation, incorrectly but it still works, and the letter is shifted appropriately relative to the other letters. My first modification to the existing code is to update the letter's COM and not send the x-translation.

Ran into a small problem when making this change in the code. Currently, the same letter, 'he', is used twice. The only difference is the x-translation. I need a new 'he', just like the old 'he', but with a different COM. Studying letter creation raised another question. Currently each letter stores a spaceLeft property. In certain cases this shifts the swatches over, so they take up less room. Should this just be incorporated in the COM? Maybe this is not an issue. As far as I can tell the only time spaceLeft is used is during word conversion to SVG. It will be added to the letter's COM along with the rest of the x-translations.

The code has been changed: 'he1' and 'he2' have now been made. The x-translations needed to write a word have been added to the initial COM's for all seven letters. The javascript is now running to completion after a few tweaks, but ...

There is no graphical output. The SVG was printed to the console and all of the swatches consist of nothing but zeros! This bug might take a while to track down. Found the bug resulting in zeroes. I had size and rotation params switched. This was fixed and now the word is written, but all the letters sit on top of one another. This bug was also traced. The letter's centers of mass are updated appropriately, however rendering a letter uses only the original swatches defined for the letter. The letter.createSVG() function does not utilize the letter's center of mass.

I need to either change letter() or pass parameters. I still prefer the center of mass approach. Changing letter() requires updating the swatches when the center of mass is modified. This makes perfect sense in retrospect! Adding the modifications to the swatches was accomplished. After fixing the few bugs the code ran. Only the first letter, dalet, is written to the screen?! So far I have not been able to trace the source of the problem.

Some time was spent moving the single letter word around in a larger box, but only one letter was visible. I watched as the SVG was produced and all of the SVG seemed to be produced by renderWord(). I then printed the SVG in the console and pasted it into TextEdit for both the individual letters and for the word. The idea was to determine where the SVG was incorrectly written. That is not the problem. The problem is that all letters beyond the first letter, dalet, have impossibly sized coordinates. Dalet's range from 20-50, chet's from 10,000-11,000, dalet's from 14,000-26,000, he1's from 24,000-36,000, waw's from 23,000-38,000, he2's from 35,000-52,000, and yod's from 27,000-28,000. Where did a factor of 10,000 come from?

Switching from a scale of 1 to 0.1 results in a factor of 1000 for letters after dalet. At a scale of 0.01 dalet has been reduced to a dot. Twelve other dots can be seen on a line extending to the right of dalet. The rest of the dots are not appearing as letters, but as fragments. This is because not all of the SVG coordinates are multiplied by the factor. For instance, at scale 1 chet's first path is:

path d= \"M 11002.54742039847 0.054999999999999716 C 11006.78580219543 16.473907385791712, 11044.202579601528 -9.17385213706753, 11039.699910220823 21.505\" stroke=\"black\" fill=\"transparent\"

The problem was found, but the solution caused another problem. All of the swatches as initially designed are tiny. Scaling them up to size is done with a factor of 220 in swatch.positionSwatch(). Scaling was done after rotation and translation. Once the swatch had been translated by the amount listed in word.letterPos the new x-coordinates were multiplied by 220 leading to the ridiculously sized control point coordinates.

The first solution was switching the order of translation and scaling in swatch.positionSwatch(). this correctly places the letters relative to one another. But the swatches within each letter are no longer in the correct position. The second approach seems more reasonable. Scale the word.letterPos amounts by 220. Whoops! That didn't work. Many of the SVG coordinates are off by thousands. Something is missing here. The first letter's SVG coordinates in the original were correct, even though its translation is listed as -30. Why did this translation not have the same impact as the second letter's, whose translation is 10? And why does translation work when just one letter is translated? The swatches composing each letter were translated earlier when modifying the COM!

The problem may be arising because the translations are added at the time of swatch.positionSwatch(). The swatch is still its initial tiny size. The added coordinates overwhelm the initial location of the control points. This translation needs to happen after the swatch is positioned relative to the other swatches in the letter. updateComs() is the culprit. This movement is taking place at the time of swatch creation and this is much too early.

Moving the letters into their word positions should probably take place at letter.createSVG(). This is the easiest place to make sure it is done after the swatch.positionSwatch() call. Instead of a letter have the spaceLeft property, which is meaningless with no adjacent letters, letters should have the wordPosition property. This can be added to any user assigned x-translation and implemented after swatch.positionSwatch(). The COM can be updated as needed. The following mods were made: updateComs() was commented out and the needed letter shifts were added to the x-translation info supplied by the user.

Success!!!

The problems that occur due to this change now need to be remedied. Scaling the word does not scale the between letter amounts. They need to be scaled with the user supplied scaling factor. This was easily accomplished in createWord(). Both x- and y-translations work fine. Rotation does not work. Each letter is rotated, but the word is still printed on a horizontal line. This could be done in createWord(), by calculating the appropriate x- and y- translations due to rotation about the word's COM and adding them at this point. The first pass at implementing this sort of works. The line of letters is rotated, but the letters are somewhat jumbled. A 90° rotation does not give a vertical line of letters, though letters are on their sides!?

Part of the problem was that the positions within the word were being added twice. After removing letterPos if a rotation was imminent, the letters were spaced reasonably well. There is still some discrepancy in the vertical positions, which is magnified as the amount of rotation increases. Though the biggest discrepancy comes between the two words. All of the letter spacing was included for the rotation and this improved alignment. Now only three letters are off: aleph, waw, and yod. For positive rotations those letters are down. For negative rotations they are too high.

It is not an issue with the centers of mass of the letters. Changing these had no apparent effect on letter positions in the rotated word. On the other hand changing the COMs of the other four letters: dalet, chet, he1, and he2, did have a positive effect. Now all of the letters are reasonably well aligned. Waw is a little off, so its COM was tweaked until it looked good! Should probably check all of the centers of mass and make sure they are as accurate as needed.



Looking at sizes of words to use to make words, it quickly became obvious that the smaller words needed to be 1/10 or less the size of the word to be filled. Using this size leads to three or four words filling the larger swatch. This does not fill the corners of the swatch. Little black corners will be needed to make up this gap. The swatches come in multiple sizes, so some allowance needs to account for this. Either the filling words need to come in different sizes or the swatch needs to be scaled on demand.

A scalable corner needs to be made, presumably with SVG. The corner may not need to be filled. Not sure if open or filled will look best at this point. Would a sufficiently segmented line be sufficient for our needs? We might even be able to fill it. Use polyline for the former and polygon for filling. Similarly, the joins between lines and line ends may need to be rounded in CSS. Finally, the corners might be rendered best by paths consisting of arcs or bézier curves.

<polyline points="x1 y1, x2 y2, x3 y3, ..."/>

svg {

  stroke-linejoin: round;

  stroke-linecap: round;

}